(ns util.pdh2
  (:use [jna.win.pdh :exclude [pdh-query pdh-objects]]
        util.os
        [util.utils :only [exception-nil no-exception-thrown?]]))

(when-not (windows?) (throw (Exception. "pdh could only work on Windows.")))

(defn pdh-command [& args]
  (throw (UnsupportedOperationException. "pdh-command")))

(defn pdh-command-line [line]
  (throw (UnsupportedOperationException. "pdh-command-line")))

;;;

(defn new-pdh []
  nil)

(def ^:dynamic *pdh* (new-pdh))

(defn pdh []
  (or *pdh* (new-pdh)))

;;;

(defn pdh-fmt-raw [value]
  value)

(defn pdh-fmt-long [value]
  (long value))


(def ^:dynamic *pdh-value-format* pdh-fmt-long)

(defmacro pdh-as-raw [& body]
  `(binding [*pdh-value-format* pdh-fmt-raw]
    ~@body))

(defmacro pdh-as-long [& body]
  `(binding [*pdh-value-format* pdh-fmt-long]
    ~@body))

;;;

(defn pdh-query
  ([query]
    (let [q (pdh-register query)] ; keep query
      (jna.win.pdh/pdh-query q)))
  ([object key]
    (pdh-query (format "\\%s\\%s" object key)))
  ([object instance key]
    (pdh-query (format "\\%s(%s)\\%s" object instance key))))

(defn pdh-objects []
  (jna.win.pdh/pdh-objects))

(defn pdh-instances [object]
  (first (pdh-query-object object)))

(defn pdh-keys [object]
  (second (pdh-query-object object))) ; counter

;;; common functions

(defn pdh-process [pid key]
  (pdh-query "Process" pid key))

(defn pdh-process-id [pid]
  (pdh-process pid "ID Process"))

(defn- annotate-processes [processes]
  (loop [left processes
         result []
         counter {}]
    (if (empty? left)
      result
      (let [p (first left)
            c (counter p)
            px (if c (str p "#" c) p)]
        (recur (rest left) (conj result px) (assoc counter p (if c (inc c) 1)))))))

(defn ^:dynamic pdh-process-instance [pid]
  (first (filter #(= pid (exception-nil (pdh-process-id %))) (annotate-processes (pdh-instances "Process")))))

(defmacro pdh-with-pid-cache [& body]
  `(binding [pdh-process-instance (memoize pdh-process-instance)]
    ~@body))

(defn pdh-working-set [process]
  (pdh-process process "Working Set"))

(defn pdh-private-bytes [process] ; virtual memory in task manager
  (pdh-process process "Private Bytes"))

(defn pdh-working-set-private [process] ; memory in task manager
  (pdh-process process "Working Set - Private"))

(defn- test-counter [q]
  (no-exception-thrown? (pdh-remove-counter (pdh-add-counter q))))

(when (and (not (test-counter "\\Process(_Total)\\Working Set - Private"))
           (test-counter "\\Process(_Total)\\Working Set"))
  (def pdh-working-set-private pdh-working-set))

(defn pdh-process-cpu [process] ; XXX: TODO: FIXME: not working:(
  (pdh-process process "% Processor Time"))

(defn pdh-total-cpu []
  (pdh-query "Processor" "_Total" "% Processor Time"))

(defn pdh-disk-io [] ; in MB
  (pdh-query "LogicalDisk" "_Total" "Disk Transfers/sec"))

(defn pdh-disk-read [] ; in MB
  (pdh-query "LogicalDisk" "_Total" "Disk Reads/sec"))

(defn pdh-disk-read-bytes [] ; in bytes
  (pdh-query "LogicalDisk" "_Total" "Disk Read Bytes/sec"))

(defn pdh-disk-write [] ; in MB
  (pdh-query "LogicalDisk" "_Total" "Disk Writes/sec"))

(defn pdh-disk-write-bytes [] ; in bytes
  (pdh-query "LogicalDisk" "_Total" "Disk Write Bytes/sec"))


(defn pdh-network-interfaces []
  (remove #(.startsWith % "isatap.") (pdh-instances "Network Interface")))

(defn pdh-network-total-bytes [interface]
  (pdh-query "Network Interface" interface "Bytes Total/sec"))

(defn pdh-network-received-bytes [interface]
  (pdh-query "Network Interface" interface "Bytes Received/sec"))

(defn pdh-network-sent-bytes [interface]
  (pdh-query "Network Interface" interface "Bytes Sent/sec"))

(defmacro with-pdh-box [& body]
  `(pdh-with-query-cache
    (pdh-with-pid-cache
      ~@body)))

(comment
(with-pdh-box
  (dotimes [i 100]
    (println "CPU" (pdh-total-cpu))
    (println (format "Disk %5s %5s %10s %5s %10s" (pdh-disk-io) (pdh-disk-read) (pdh-disk-read-bytes) (pdh-disk-write) (pdh-disk-write-bytes)))
  (doseq [i (filter #(.startsWith % "Dell") (pdh-network-interfaces))]
    (println (format "Network: %40s %10s %10s %10s"
      i (pdh-network-total-bytes i) (pdh-network-received-bytes i) (pdh-network-sent-bytes i))))
    (Thread/sleep 1000)
    (pdh-collect-all)))
)
